Bert相关——(5)Pre-train Model

引言

过去NLP领域通常是一个任务一个模型,但今天已经逐渐迈向:模型先了解普遍的语言,再去解各式各样的NLP任务——pre-train+fine tuning范式

根据大量无标注的文字资料来训练一个模型,希望这个模型能读懂文字,这个训练过程就叫Pre-train预训练。接下来针对不同的下游任务再收集少量对应的、有标注的训练资料去Fine-tune微调预训练的模型,然后就可以让机器去完成不同的下游任务。

之前介绍了BERT的原理,BERT可以用于Pre-train+fine tuning范式。

这篇先介绍BERT的pre-train方法,并介绍如何利用HuggingFace(抱抱脸)的Transformers包进行BERT预训练。

Pre-train

Pre-train Model做的是”represent each token by a embedding vector“,这个vector应该包含了这个token的语义,意思相近的token应该有相似的embedding,且embedding的某些维度应该是代表某些特定的语义。

之前在BERT相关——(2)Contextualized Word Embedding和ELMO模型)中提到过,word2vec,Glove等,就是经典的获取word embedding的预训练模型。

BERT也是其中一种Pre-train model,可以用于学习Contextualized word embedding

BERT for Pre-train

根据之前写的博客BERT相关——(3)BERT模型,我们用Transformers实现BERT模型进行预训练需要以下几个步骤:

  1. 利用Tokenizer对语料分词;
  2. 重新配置模型;
  3. 编写满足训练任务的处理代码:每个句子进行掩膜并组成句子对的正负样本集合以完成BERT训练的两个任务;
  4. 数据输入模型进行训练。

利用Tokenizer对语料分词

这里提供四种训练自己的Tokenizer的方法:

方法1:

来自官方最新文档(截至2021-08-22)

from tokenizers import Tokenizer
from tokenizers.models import BPE
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))

from tokenizers.trainers import BpeTrainer
trainer = BpeTrainer(special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])

from tokenizers.pre_tokenizers import Whitespace
tokenizer.pre_tokenizer = Whitespace()
#语料库文件
files = [f"data/wikitext-103-raw/wiki.{split}.raw" for split in ["test", "train", "valid"]]
tokenizer.train(files, trainer)
tokenizer.save("data/tokenizer-wiki.json")
#加载Tokenizer
tokenizer = Tokenizer.from_file("data/tokenizer-wiki.json")

方法2:

来自:HuggingFace官方:How to train a language model from scratch

%%time 
from pathlib import Path

from tokenizers import ByteLevelBPETokenizer

paths = [str(x) for x in Path(".").glob("**/*.txt")]

# Initialize a tokenizer
tokenizer = ByteLevelBPETokenizer()

# Customize training
tokenizer.train(files=paths, vocab_size=52_000, min_frequency=2, special_tokens=[
"[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"
])
tokenizer.save_model("BERT")
from tokenizers.implementations import ByteLevelBPETokenizer
from tokenizers.processors import BertProcessing


tokenizer = ByteLevelBPETokenizer(
"./BERT/vocab.json",
"./BERT/merges.txt",
)

方法3:

来自:使用huggingface的Transformers预训练自己的bert模型+FineTuning稍作修改。

import tokenizers
# 创建分词器
bwpt = tokenizers.BertWordPieceTokenizer()
filepath = "../excel2txt.txt" # 语料文件
#训练分词器
bwpt.train(
files=[filepath],
vocab_size=50000, # 这里预设定的词语大小不是很重要
min_frequency=1,
limit_alphabet=1000,
special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"]
)
# 保存训练后的模型词表
bwpt.save_model('./pretrained_models/')
#output: ['./pretrained_models/vocab.txt']

# 加载刚刚训练的tokenizer
tokenizer=BertTokenizer(vocab_file='./pretrained_models/vocab.txt')

如果已经有了词汇表,不需要去训练分词,可以直接用最后一句代码加载词汇表,得到对应的tokenizer,注意里面需要添加"[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"等特殊词

方法4:

来自使用huggingface的Transformers预训练自己的bert模型+FineTuning稍作修改。

from tokenizers import Tokenizer
from tokenizers.decoders import ByteLevel as ByteLevelDecoder
from tokenizers.models import BPE
from tokenizers.normalizers import Lowercase, NFKC, Sequence
from tokenizers.pre_tokenizers import ByteLeve
from tokenizers.trainers import BpeTrainer

# 1、创建一个空的字节对编码模型
tokenizer = Tokenizer(BPE())

#2、启用小写和unicode规范化,序列规范化器Sequence可以组合多个规范化器,并按顺序执行
tokenizer.normalizer = Sequence([
NFKC(),
Lowercase()
])
#3、标记化器需要一个预标记化器,负责将输入转换为ByteLevel表示。
tokenizer.pre_tokenizer = ByteLevel()

# 4、添加解码器,将token令牌化的输入恢复为原始的输入
tokenizer.decoder = ByteLevelDecoder()
# 5、初始化训练器,给他关于我们想要生成的词汇表的详细信息
trainer = BpeTrainer(vocab_size=858, show_progress=True, initial_alphabet=ByteLevel.alphabet())
# 6、开始训练我们的语料
tokenizer.train(files=["./tmp/all_data_txt.txt"], trainer=trainer,special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])
# 最终得到该语料的Tokenizer,查看下词汇大小
print("Trained vocab size: {}".format(tokenizer.get_vocab_size()))
# 保存训练的tokenizer
tokenizer.save('./my_token/')

上述的训练过程其实是针对的是byte级别的BPE编码,tokenizer保存为两个文件,分别是vocab.json, merges.txtmerges.txt文件在Robert中会用到,,普通的bert只需要vocab。这两个文件,可以用官方封装好的Tokenizer直接加载,如下:

from tokenizers.implementations import ByteLevelBPETokenizer
from tokenizers.processors import BertProcessing
tokenizer = ByteLevelBPETokenizer(
"vocab.json",
"merges.txt",
)

让我们用官方的小例子来试验一下:

output = tokenizer.encode("Hello, y'all! How are you 😁 ?")
print(output.tokens)
# ["Hello", ",", "y", "'", "all", "!", "How", "are", "you", "[UNK]", "?"]

重新配置模型

from transformers import (
CONFIG_MAPPING,MODEL_FOR_MASKED_LM_MAPPING, AutoConfig,
AutoModelForMaskedLM, AutoTokenizer,DataCollatorForLanguageModeling,HfArgumentParser,Trainer,TrainingArguments,set_seed,
)
# 自己修改部分配置参数
config_kwargs = {
"cache_dir": None,
"revision": 'main',
"use_auth_token": None,
"hidden_size": 512,
"num_attention_heads": 4,
"hidden_dropout_prob": 0.2,
"vocab_size": 863 # 自己设置词汇大小
}
# 将模型的配置参数载入
config = AutoConfig.from_pretrained('./tmp/bert-base-case/', **config_kwargs)
# 载入预训练模型,这里其实是根据某个模型结构调整config然后创建模型
model = AutoModelForMaskedLM.from_pretrained(
'../tmp/bert-base-case/',
from_tf=bool(".ckpt" in 'roberta-base'), # 支持tf的权重
config=config,
cache_dir=None,
revision='main',
use_auth_token=None,
)
model.resize_token_embeddings(len(tokenizer))
#output:Embedding(863, 768, padding_idx=1)

编写满足训练任务的处理代码

Next Sentence Prediction(NSP)任务

我们可以对输入进行预处理,比如在BERT中就需要构建句子对用于Next Sentence Prediction(NSP)任务。

from tokenizers.processors import TemplateProcessing

tokenizer.post_processor = TemplateProcessing(
single="[CLS] $A [SEP]",
pair="[CLS] $A [SEP] $B:1 [SEP]:1",
special_tokens=[
("[CLS]", tokenizer.token_to_id("[CLS]")),
("[SEP]", tokenizer.token_to_id("[SEP]")),
],
)
#设置句子最大长度
tokenizer.enable_truncation(max_length=512)
#使用tokenizer.save()保存模型
tokenizer.save("data/tokenizer-wiki.json")

Masked Language Model(MLM)任务

Masked Language Model(MLM)任务在DataCollatorForLanguageModeling中进行设置。

from transformers import PreTrainedTokenizerFast
#注意这里用了另外一种方式加载Tokenizer
tokenizer = PreTrainedTokenizerFast(tokenizer_file="data/tokenizer-wiki.json")

from transformers import DataCollatorForLanguageModeling
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer, mlm=True, mlm_probability=0.15 #mlm表示是否使用masked language model;mlm_probability表示mask的几率
)

训练

加载数据集

我们用LineByLineTextDataset加载数据集。

%%time
from transformers import LineByLineTextDataset

dataset = LineByLineTextDataset(
tokenizer=tokenizer,
file_path="./oscar.eo.txt", #数据集文件,一行为一句
block_size=128,
)

开始训练

from transformers import Trainer, TrainingArguments

training_args = TrainingArguments(
output_dir="./BERT",
overwrite_output_dir=True,
num_train_epochs=1,
per_gpu_train_batch_size=64,
save_steps=10_000,
save_total_limit=2,
prediction_loss_only=True,
)

trainer = Trainer(
model=model,
args=training_args,
data_collator=data_collator,
train_dataset=dataset,
)

#开始训练
%%time
trainer.train()
#保存模型
trainer.save_model("./BERT")

快速检查模型是否训练

除了查看训练和评估损失下降之外,检查我们的语言模型是否正在学习任何有趣的东西的最简单方法是通过 FillMaskPipeline。

语法检查

语法检查,用于查看mask的位置是否能输出一个正确词性的单词:

from transformers import pipeline

fill_mask = pipeline(
"fill-mask",
model="./BERT",
tokenizer="./BERT"
)
# 语法检查,查看mask的位置是否能输出一个正确词性的单词
# The sun <mask>.
fill_mask("La suno <mask>.")

参考文献

课程向:深度学习与人类语言处理 ——李宏毅,2020 (P19)

HuggingFace官方:How to train a language model from scratch

使用huggingface的Transformers预训练自己的bert模型+FineTuning